#include <QFileDialog>
#include <QDesktopServices>
#include <QUrl>
#include <QMessageBox>
#include <QFileInfo>
#include <QFile>
#include <QTimer>
#include <QDebug>
#include <QSettings>
#include <QInputDialog>

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "defines.h"
#include "generatorfileoutput.h"
#include "settingsdialog.h"
#include "common/networksettingsdialog.h"
#include "settings.h"
#include "rcsession.h"

#include "qtprojectgeneratorfactory.h"

#include "symbian/symbiandefines.h" // TODO this should be removed eventually

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    m_generator(NULL),
    m_statusUpdater(NULL),
    m_rcSession(NULL)
{
    ui->setupUi(this);

    // Init variables
    m_buildEnabled = false;
    m_rebuildEnabled = false;

    // Remote generator
    // TODO: no more acting as a server, this can be moved out from here
    m_remote = new RemoteGenerator(this);

    // Init log file. UI progress bar is tied to log file updates.
    m_outputView = new GeneratorFileOutput(Settings::get(Settings::LogFilePath).toString());

    m_rcSession = new RcSession(*m_outputView,
                                this);

    buildPlatformsMenu();

    // Init status bar with a progress bar
    m_statusBarLabel = new QLabel(this);
    m_statusBarLabel->setText(tr("Idle"));
    statusBar()->addWidget(m_statusBarLabel, 1);
    m_progressBar = new QProgressBar(this);
    m_progressBar->setVisible(false);
    m_progressBar->setRange(0, m_statusUpdater->maximumProgress());
    m_progressBar->setValue(0);
    statusBar()->addWidget(m_progressBar, 1);

    // Set icons. These are LGPL and *have* to be loaded at run-time!
    // Using LGPL compiled in the binary (i.e. Qt resource file) would risk causing
    // the whole binary to be LGPL. The current icon set is from Oxygen Project,
    // licensed under LGPL v3.
    ui->actionOpenSettings->setIcon(QIcon("oxygen-icons/configure.svgz"));
    ui->actionConnectToRemoteCompiler->setIcon(QIcon("oxygen-icons/network-wired.svgz"));
    ui->actionCloseProject->setIcon(QIcon("oxygen-icons/document-close.svgz"));
    ui->actionOpenWidgetFile->setIcon(QIcon("oxygen-icons/document-open.svgz"));
    ui->actionOpenURL->setIcon(QIcon("oxygen-icons/document-open-remote.svgz"));
    ui->actionOpenDirectory->setIcon(QIcon("oxygen-icons/document-open-folder.svgz"));
    ui->actionPreview->setIcon(QIcon("oxygen-icons/document-preview.svgz"));
    ui->actionBuild->setIcon(QIcon("oxygen-icons/run-build.svgz"));
    ui->actionRebuild->setIcon(QIcon("oxygen-icons/run-build-install.svgz"));

    // Prepare menu selected states
    ui->actionEnableTextSelection->setChecked(Settings::get(Settings::TextSelectionEnabled).toBool());
    ui->actionWidgetIsPannable->setChecked(Settings::get(Settings::PanningEnabled).toBool());

    // UI signals connected to slots
    connect(ui->actionExit, SIGNAL(triggered()), this, SLOT(exitApplication()));
    connect(ui->actionOpenWidgetFile, SIGNAL(triggered()), this, SLOT(openWidgetFile()));
    connect(ui->actionOpenDirectory, SIGNAL(triggered()), this, SLOT(openHTMLDirectory()));
    connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(about()));
    connect(ui->actionOpenDocumentation, SIGNAL(triggered()), this, SLOT(openDocumentation()));
    connect(ui->actionCloseProject, SIGNAL(triggered()), this, SLOT(closeProject()));
    connect(ui->actionPreview, SIGNAL(triggered()), this, SLOT(preview()));
    connect(ui->actionBuild, SIGNAL(triggered()), this, SLOT(build()));
    connect(ui->actionRebuild, SIGNAL(triggered()), this, SLOT(rebuild()));
    connect(ui->actionWidgetIsPannable, SIGNAL(triggered(bool)), this, SLOT(setPanningEnabled(bool)));
    connect(ui->actionEnableTextSelection, SIGNAL(triggered(bool)), this, SLOT(setTextSelectionEnabled(bool)));
    connect(ui->actionOpenSettings, SIGNAL(triggered()), this, SLOT(openSettingsDialog()));
    connect(ui->actionConnectToRemoteCompiler, SIGNAL(triggered()), this, SLOT(connectToRemoteCompiler()));
    connect(ui->buttonOpenLog, SIGNAL(clicked()), this, SLOT(openLogFile()));
    connect(ui->buttonOpenDirectory, SIGNAL(clicked()), this, SLOT(openBuildDirectory()));
    connect(ui->buttonInstallSis, SIGNAL(clicked()), this, SLOT(installSis()));
    connect(ui->actionOpenURL, SIGNAL(triggered()), this, SLOT(openURL()));

    connect(m_rcSession,
            SIGNAL(refreshRcPropertiesCompleted(bool, bool)),
            this,
            SLOT(rcPropertiesRefreshedSlot(bool, bool)));

    // At startup, there are no projects open
    closeProject();
    checkSettings();
}

MainWindow::~MainWindow()
{
    delete ui;

    delete m_generator;
    m_generator = 0;

    delete m_statusUpdater;
    m_statusUpdater = 0;

    delete m_outputView;
    m_outputView = 0;
}

/**
 * Change event handler
 */
void MainWindow::changeEvent(QEvent *e)
{
    QMainWindow::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
    }
}

/**
 * Closes the application
 */
void MainWindow::exitApplication()
{
	QCoreApplication::exit(0);
}

/**
 * Gives the user a dialog that is used to select a file
 */
void MainWindow::openWidgetFile()
{
	QString lastPath = Settings::get(Settings::LastWidgetPath).toString();
	QString fileName = QFileDialog::getOpenFileName(this, tr("Select Widget File"), lastPath, tr("Widget Files") + " (*.wgz *.wgt);;" + tr("All Files (*)"));
	if (!fileName.isEmpty())
	{
		closeProject();
		Settings::set(Settings::LastWidgetPath, QDir::toNativeSeparators(QFileInfo(fileName).absolutePath()));
		setActivePath(fileName);
	}
}

/**
 * Gives the user a dialog requesting URL to be fetched
 */
void MainWindow::openURL()
{
	QUrl url = QUrl::fromUserInput(QInputDialog::getText(this, tr("Give URL"), "Give URL of the page to be fetched:"));
	if (url.isValid())
	{
		closeProject();
		connect(m_remote, SIGNAL(message(QString)), m_outputView, SLOT(printOutput(QString)));
		connect(m_remote, SIGNAL(finished()), this, SLOT(remoteContentFetched()));
		m_progressBar->setRange(0, 0);
		m_statusBarLabel->setVisible(false);
		m_progressBar->setVisible(true);
		ui->labelStatusValue->setText("Fetching " + url.toString());
		m_remote->getPage(url.toString(), url.host());
	}
	else if (!url.isEmpty())
	{
		QMessageBox::critical(this, tr("Invalid URL"), tr("The given URL is not valid."));
	}
}

/**
 * Remote content fetch is finished, reflect in the UI
 */
void MainWindow::remoteContentFetched()
{
	m_progressBar->setRange(0, m_statusUpdater->maximumProgress());
	m_progressBar->setValue(m_statusUpdater->maximumProgress());
	ui->labelStatusValue->setText(tr("Content fetch done"));
}

/**
 * Gives the user a dialog that is used to select a directory
 */
void MainWindow::openHTMLDirectory()
{
	QString lastPath = Settings::get(Settings::LastDirectoryPath).toString();
	QString directoryName = QFileDialog::getExistingDirectory(this, tr("Select HTML Directory. The application will be generated from the contents of the selected directory."), lastPath);
	if (!directoryName.isEmpty())
	{
		closeProject();
		Settings::set(Settings::LastDirectoryPath, QDir::toNativeSeparators(directoryName));
		setActivePath(directoryName);
	}
}

/**
 * Sets the path to the opened file/directory. This will be called
 * also from the remote generator.
 */
void MainWindow::setActivePath(QString path)
{
	m_currentFileOrDirectory = path;
	ui->labelProjectValue->setText(QDir::toNativeSeparators(path));
	setBuildActionsEnabled(true);
}

/**
 * Opens a pre-defined URL in a system's preferred browser
 */
void MainWindow::openDocumentation()
{
	QString documentation = "HAG User Guide.pdf";
	if (!QFile::exists(documentation))
	{
		// PDF does not exist any more
		QMessageBox::critical(this, "Documentation not found", "Documentation file was not found! Please reinstall application.");
	}
	else
	{
		// Just presume the user has a PDF viewer installed
		QDesktopServices::openUrl(QUrl::fromLocalFile(documentation));
	}
}

/**
 * Calls the SIS, and if the user has PC Suite installed, it will
 * prompt the installation
 */
void MainWindow::installSis()
{
    QFileInfo
        pkgFile = m_statusUpdater->pkgFileInfo();

    IQtProjectGenerator::PkgInstallResult
        result = m_generator->installPkg(pkgFile);

    // TODO maybe some action about supportedness / success
    Q_UNUSED(result)
}

/**
 * Shows "about" message box
 */
void MainWindow::about()
{
	QMessageBox aboutBox;
	QPixmap pixmap(":/gfx/qt.svg");
	pixmap = pixmap.scaled(QSize(100, 100), Qt::KeepAspectRatio, Qt::SmoothTransformation);
	aboutBox.setIconPixmap(pixmap);
	aboutBox.setWindowTitle(tr("About Hybrid Application Generator"));
	aboutBox.setText(tr("Hybrid Application Generator 0.1"));
	aboutBox.setInformativeText("Built on Apr 14 2010\n\nCopyright 2009-2010 Nokia Corporation. All rights reserved.\n\nThe program is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.");
	aboutBox.setStyleSheet("#qt_msgbox_label { font-weight: bold; font-size: 10pt; }");
	aboutBox.exec();
}


void MainWindow::platformChosen()
{
    QAction
        * action = qobject_cast<QAction*>(sender());
    if (action != NULL)
    {
        QString
            platformId = action->data().toString();

        setPlatform(platformId);

        m_progressBar->setRange(0, m_statusUpdater->maximumProgress());
        m_progressBar->setValue(0);
    }
}


/**
 * Enable actions that are directly related to a widget only if a
 * widget is currectly open
 */
void MainWindow::setBuildActionsEnabled(bool enabled)
{
	// Set new value buildEnabled; rebuildEnabled also has
	// to be allowed beforehand to be allowed now
	m_buildEnabled = enabled;
	m_rebuildEnabled = m_rebuildEnabled && m_buildEnabled;

	if (enabled == false)
	{
		ui->labelStatusValue->setText(tr("Idle"));
		m_statusBarLabel->setText(tr("Idle"));
		ui->labelLogValue->setText(tr("<no log available>"));
		ui->buttonOpenDirectory->setEnabled(false);
        ui->buttonInstallSis->setEnabled(false);
		m_progressBar->setVisible(false);
		m_statusBarLabel->setVisible(true);
	}

	// Check that settings allow building
	checkSettings();

	// Actions are enabled only when a project is open
	ui->actionCloseProject->setEnabled(enabled);
}

/**
 * Closes the project, i.e. clear selected file and disables
 * relevant menu items.
 */
void MainWindow::closeProject()
{
    m_generator->closeProject();
    m_currentFileOrDirectory = "";
	ui->labelProjectValue->setText(tr("<no file selected>"));
	setBuildActionsEnabled(false);
}

/**
 * If the selected file / folder still exists, prepares the necessary
 * folder structure and opens an external application that shows the
 * widget-to-be in a window that is sized to match device resolution
 */
void MainWindow::preview()
{
	// Check that file exists
	if (QFile::exists(m_currentFileOrDirectory))
	{
		m_generator->preview(m_currentFileOrDirectory);
	}
}

/**
 * Extract, prepare, and build the selected file / folder
 */
void MainWindow::build()
{
        m_generator->build(m_currentFileOrDirectory,
                           *m_rcSession);
}

/**
 * Rebuild without cleaning the target folder structure first.
 * Can be used to keep changes if e.g. widget content is changed
 * directly in the build directory -- otherwise the target would
 * be deleted and replaced with fresh source files.
 */
void MainWindow::rebuild()
{
        m_generator->rebuild(*m_rcSession);
}

/**
 * Enables / disables the option to enable panning canvas in widgets
 */
void MainWindow::setPanningEnabled(bool enabled)
{
	Settings::set(Settings::PanningEnabled, enabled);
}

/**
 * Enables / disables the option to disable text selection in widgets
 */
void MainWindow::setTextSelectionEnabled(bool enabled)
{
	Settings::set(Settings::TextSelectionEnabled, enabled);
}

/**
 * Update the UI when the generation progresses
 */
void MainWindow::progressUpdated()
{
	m_statusBarLabel->setVisible(false);
	m_progressBar->setVisible(true);
	m_progressBar->setValue(m_statusUpdater->currentProgress());
	ui->labelStatusValue->setText(m_statusUpdater->statusText());
	ui->labelLogValue->setText(tr("Logged") + " " + QString::number(m_statusUpdater->logSize()) + " " + tr("rows"));
}

/**
 * When the generation progress is done, update the UI
 * to reflect the completed state
 */
void MainWindow::progressFinished(bool success)
{
	m_rebuildEnabled = m_buildEnabled;
	ui->actionRebuild->setEnabled(m_buildEnabled);
	ui->buttonOpenDirectory->setEnabled(true);
    if (success && m_generator->getTarget() == BUILD)
    {
        ui->buttonInstallSis->setEnabled(true);
    }
}

/**
 * Opens the log file in an external viewer.
 */
void MainWindow::openLogFile()
{
	QDesktopServices::openUrl(QUrl::fromLocalFile(Settings::get(Settings::LogFilePath).toString()));
}

/**
 * Opens the settings dialog that has further configurations
 */
void MainWindow::openSettingsDialog()
{
    SettingsDialog
        sd(m_generator,
           m_currentFileOrDirectory,
           this);
    sd.exec();

    checkSettings();
}


/**
  * Opens the network settings dialog.
  */
void MainWindow::connectToRemoteCompiler()
{
    NetworkSettingsDialog
        nsd(m_rcSession,
            this);
    nsd.exec();

    if (nsd.justSignedIn())
    {
        m_rcSession->refreshRcProperties();
    }

    checkSettings();
}


/**
 * Check settings that has to be correct before building can proceed.
 */
void MainWindow::checkSettings()
{
    QStringList
        errors = m_generator->errorsForTarget(BUILD);

    if (errors.empty())
    {
        ui->labelBuildValue->setText(Settings::get(Settings::QtVersionString).toString());
        ui->labelSettingsValue->setStyleSheet("");
        ui->labelSettingsValue->setText("Settings OK");
    }
    else
    {
        ui->labelBuildValue->setText("<not available>");
        ui->labelSettingsValue->setStyleSheet("QLabel { color: red; }");
        ui->labelSettingsValue->setText(errors.at(0));
    }

    // The preview Qt version is the version with which the generator is compiled
    ui->labelPreviewValue->setText("4.6.2");

    // Settings affect the building: disable if qtenv.bat is not configured
    ui->actionPreview->setEnabled(m_buildEnabled && errors.empty());
    ui->actionBuild->setEnabled(m_buildEnabled && errors.empty());
    ui->actionRebuild->setEnabled(m_rebuildEnabled && errors.empty());
}


void MainWindow::setPlatform(QString platformId)
{
    if (platformId != m_platformId || m_generator == NULL)
    {
        delete m_generator;
        m_generator = 0;
        delete m_statusUpdater;
        m_statusUpdater = 0;

        m_platformId = platformId;

        QtProjectGeneratorFactory
            generatorFactory;
        m_generator = generatorFactory.createProjectGenerator(m_platformId,
                                                              *m_outputView,
                                                              NULL); // parent

        // Status updater
        m_statusUpdater = m_generator->createStatusUpdater(NULL); // parent
        connect(m_statusUpdater, SIGNAL(updated()), this, SLOT(progressUpdated()));
        connect(m_statusUpdater, SIGNAL(finished(bool)), this, SLOT(progressFinished(bool)));

        setActivePath(m_currentFileOrDirectory);
    }
}


void MainWindow::openBuildDirectory()
{
	QDesktopServices::openUrl(QUrl::fromLocalFile(m_generator->generatorData().projectDirectory()));
}


void MainWindow::rcPropertiesRefreshedSlot(bool success,
                                           bool hasRcPropertiesChanged)
{
    if (success)
    {
        if (hasRcPropertiesChanged)
        {
            buildPlatformsMenu();

            QMessageBox::information(this,
                                     "Connecting Remote Compiler Succeeded",
                                     "Please check build menu for potential\n"
                                     "new remote build targets",
                                     QMessageBox::Ok);
        }
    }
    else
    {
        QMessageBox::warning(this,
                             "Remote Compiler Request Failed",
                             "Querying Remote Compiler for supporter OS/QT platforms failed.",
                             QMessageBox::Ok);
    }
}


void MainWindow::buildPlatformsMenu()
{
    // we clear up the build menu after the second separator
    QObjectList
        buildMenuKids = ui->menuBuild->children();
    foreach (QObject * kid, buildMenuKids)
    {
        QAction
            * kidAction = qobject_cast<QAction*>(kid);
        if (kidAction == NULL)
            continue;
        if (kidAction->data().toString().indexOf(DYN_BUILDTARGET) == 0)
        {
            ui->menubar->removeAction(kidAction);
            delete kidAction;
        }
    }

    // Getting platform infos, and building menu items & actions for them
    QList<QAction*>
        platformActions;
    QString
        defaultPlatformId;

    QtProjectGeneratorFactory
        generatorFactory;
    QList<PlatformInfo>
        platformInfos = generatorFactory.supportedPlatforms();
    QList<PlatformInfo>::const_iterator
        piIt = platformInfos.begin(),
        piEnd = platformInfos.end();

    for (; piIt != piEnd; ++piIt)
    {
        QAction
            * action = new QAction(piIt->m_platformName,
                                   ui->menuBuild);
        action->setData(piIt->m_platformId);
        action->setCheckable(true);
        // OBS action->setStatusTip(tr("Choose platform to build for"));
        action->setStatusTip(piIt->m_shortDescription);
        connect(action,
                SIGNAL(triggered()),
                this,
                SLOT(platformChosen()));
        platformActions.push_back(action);

        if (piIt->m_isDefault)
        {
            action->setChecked(true);
            defaultPlatformId = piIt->m_platformId;
        }
    }

    // QActionGroup makes these platform menu items behave like "radio buttons"
    // only one can be selected at a time
    QActionGroup
        * platformActionGroup = new QActionGroup(this);
    QList<QAction*>::const_iterator
        paIt = platformActions.begin(),
        paEnd = platformActions.end();
    for (; paIt != paEnd; ++paIt)
        platformActionGroup->addAction(*paIt);

    // Adding actions to the "build" menu
    paIt = platformActions.begin(),
    paEnd = platformActions.end();
    for (; paIt != paEnd; ++paIt)
        ui->menuBuild->addAction(*paIt);

    // setting the default platform name (and m_generator and m_statusUpdater
    // instances accordingly)
    setPlatform(defaultPlatformId);
}
